/*
 * topological-hex, a program to compute combinatorial hexahedral meshes.
 *
 * Copyright (C) <2018> <Université catholique de Louvain (UCL), Belgique>
 *
 * List of the contributors to the development, description and complete
 * License: see LICENSE file.
 *
 * This program (topological-hex) is free software:
 * you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see COPYING file).  If not,
 * see <http://www.gnu.org/licenses/>.
 */


#include <check.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#include <sys/wait.h>
#include <sys/socket.h>

#include "protocol.h"
#include "dfs.h"

#include "input.h"

static const char *user_names[] = {
  "Alexandre",
  "Amaury",
  "Celestin",
  "Francois",
  "Jeanne",
  "Jovana",
  "Kilian",
  "Pierre-Alexandre",
  "Ruili",
  "Ruyang",
};

bool always_true(void *data) {
  return true;
}

bool always_false(void *data) {
  return false;
}

typedef struct partial_write_arg {
  bool (*fn)(int socket, void *data);
  void *fn_data;
} partial_write_arg;

/**
 * Reads the data that a function writes, and only writes part of it back.
 *
 * This is used to ensure that reading function properly detect errors when the
 * communication is interrupted in the middle of a message.
 */
bool partial_write(int socket, void *data) {
  partial_write_arg *arg = data;

  int fd[2];
  if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1)
    return false;

  pid_t pid = check_fork();
  if (pid == -1) {
    close(fd[0]);
    close(fd[1]);
    return false;
  }

  if (pid == 0) {
    close(fd[0]);
    arg->fn(fd[1], arg->fn_data);
    close(fd[1]);
    exit(0);
  }
  else {
    close(fd[1]);
    char buffer[10 * 1024];
    ssize_t n_read  = read(fd[0], buffer, sizeof(buffer));
    ssize_t n_write = -1;
    if (n_read != -1)
      n_write = write(socket, buffer, n_read / 2);
    close(fd[0]);

    waitpid(pid, NULL, 0);
    return n_read != -1 && n_write != -1;
  }
}

typedef struct io_state {
  int socket;
  pid_t child_pid;
} io_state;

static
void run_io(io_state *state, bool (*child_fn)(int socket, void *data), void *data) {
  int fd[2];
  ck_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) != -1);

  pid_t pid = check_fork();
  ck_assert(pid != -1);

  if (pid == 0) {
    close(fd[0]);
    bool status = child_fn(fd[1], data);
    close(fd[1]);
    exit(status ? 0 : 1);
  }

  close(fd[1]);
  state->child_pid = pid;
  state->socket = fd[0];
}

typedef enum expected_status {
  STATUS_SUCCESS,
  STATUS_FAILURE,
  STATUS_IGNORE,
} expected_status;

static
void end_io(io_state *state, expected_status expected_status) {
  close(state->socket);

  int status;
  waitpid(state->child_pid, &status, 0);

  if (expected_status == STATUS_SUCCESS)
    ck_assert(WEXITSTATUS(status) == 0);
  else if (expected_status == STATUS_FAILURE)
    ck_assert(WEXITSTATUS(status) != 0);
}

static
bool write_name_fn(int socket, void *string) {
  return write_name(always_true, NULL, socket, string, strlen(string));
}

static
bool read_name_fn(int socket, void *string) {
  char buffer[256];
  return read_name(always_true, NULL, socket, buffer, sizeof(buffer));
}

static void try_read_name(size_t buffer_size, const char *string,
                          expected_status expected_status) {
  io_state state;
  run_io(&state, write_name_fn, (void*)string);

  char buffer[buffer_size];
  bool status = read_name(always_true, NULL, state.socket,
                          buffer, sizeof(buffer));
  if (expected_status == STATUS_SUCCESS) {
    ck_assert(status);
    ck_assert_str_eq(buffer, string);

    char eof_buf;
    ck_assert(read(state.socket, &eof_buf, sizeof(eof_buf)) == 0);
  }
  else
    ck_assert(!status);

  end_io(&state, expected_status);
}

START_TEST(test_send_name) {
  for (size_t i = 0; i < sizeof(user_names)/sizeof(*user_names); i++) {
    try_read_name(256, user_names[i], STATUS_SUCCESS);
    try_read_name(strlen(user_names[i]) + 1, user_names[i], STATUS_SUCCESS);
  }
} END_TEST

START_TEST(test_send_name_small_buffer) {
  for (size_t i = 0; i < sizeof(user_names)/sizeof(*user_names); i++) {
    try_read_name(4, user_names[i], STATUS_IGNORE);
    try_read_name(strlen(user_names[i]), user_names[i], STATUS_IGNORE);
    try_read_name(strlen(user_names[i]) - 1, user_names[i], STATUS_IGNORE);
  }
} END_TEST

START_TEST(test_interrupt_read_name) {
  for (size_t i = 0; i < sizeof(user_names)/sizeof(*user_names); i++) {
    io_state state;
    run_io(&state, write_name_fn, (void*)user_names[i]);
    char buffer[256];
    ck_assert(!read_name(always_false, NULL, state.socket, buffer,
                         sizeof(buffer)));
    end_io(&state, STATUS_IGNORE);
  }
} END_TEST

START_TEST(test_interrupt_write_name) {
  for (size_t i = 0; i < sizeof(user_names)/sizeof(*user_names); i++) {
    io_state state;
    run_io(&state, read_name_fn, NULL);
    ck_assert(!write_name(always_false, NULL, state.socket, user_names[i],
                          strlen(user_names[i])));
    end_io(&state, STATUS_FAILURE);
  }
} END_TEST

START_TEST(test_read_name_partial_write) {
  for (size_t i = 0; i < sizeof(user_names)/sizeof(*user_names); i++) {
    io_state state;
    partial_write_arg arg = {write_name_fn, (void*)user_names[i]};
    run_io(&state, partial_write, &arg);
    char buffer[256];
    ck_assert(!read_name(always_true, NULL, state.socket, buffer,
                         sizeof(buffer)));
    end_io(&state, STATUS_SUCCESS);
  }
} END_TEST

typedef struct command {
  message_tag tag;
  uint64_t arg;
} command;

static
bool write_command_fn(int socket, void *arg) {
  command *command = arg;
  return write_command(always_true, NULL, socket,
                       command->tag, command->arg);
}

static
bool read_command_fn(int socket, void *data) {
  message_tag tag;
  uint64_t arg;
  return read_command(always_true, NULL, socket, &tag, &arg);
}

START_TEST(test_send_command) {
  for (size_t i = 0; i < 256; i++) {
    message_tag tag = rand() % TAG_COUNT;
    uint64_t arg = rand() % (1ul << (64-TAG_BITS));

    command comm = {tag, arg};
    io_state state;
    run_io(&state, write_command_fn, &comm);
    message_tag received_tag;
    uint64_t received_arg;
    ck_assert(read_command(always_true, NULL, state.socket,
                           &received_tag, &received_arg));
    ck_assert_int_eq(received_arg, arg);
    ck_assert_int_eq(received_tag, tag);
    char buf;
    ck_assert(read(state.socket, &buf, 1) == 0);
    end_io(&state, STATUS_SUCCESS);
  }
} END_TEST

START_TEST(test_interrupt_read_command) {
    for (size_t i = 0; i < 256; i++) {
    message_tag tag = rand() % TAG_COUNT;
    uint64_t arg = rand() % (1ul << (64-TAG_BITS));

    command comm = {tag, arg};
    io_state state;
    run_io(&state, write_command_fn, &comm);
    message_tag received_tag;
    uint64_t received_arg;
    ck_assert(!read_command(always_false, NULL, state.socket,
                            &received_tag, &received_arg));
    end_io(&state, STATUS_IGNORE);
  }
} END_TEST

START_TEST(test_interrupt_write_command) {
    for (size_t i = 0; i < 256; i++) {
    message_tag tag = rand() % TAG_COUNT;
    uint64_t arg = rand() % (1ul << (64-TAG_BITS));

    io_state state;
    run_io(&state, read_command_fn, NULL);
    ck_assert(!write_command(always_false, NULL, state.socket,
                             tag, arg));
    end_io(&state, STATUS_FAILURE);
  }
} END_TEST

START_TEST(test_read_command_partial_write) {
    for (size_t i = 0; i < 256; i++) {
    message_tag tag = rand() % TAG_COUNT;
    uint64_t arg = rand() % (1ul << (64-TAG_BITS));
    command comm = {tag, arg};

    partial_write_arg write_arg = {write_command_fn, &comm};

    io_state state;
    run_io(&state, partial_write, &write_arg);
    message_tag received_tag;
    uint64_t received_arg;
    ck_assert(!read_command(always_true, NULL, state.socket,
                             &received_tag, &received_arg));
    end_io(&state, STATUS_SUCCESS);
  }
} END_TEST

static
bool write_layout_fn(int socket, void *arg) {
  subproblem_layout *layout = arg;
  return write_layout(always_true, NULL, socket,
                      layout->num_vertices, layout->num_quads,
                      layout->num_hexes, layout->num_symmetries,
                      layout->num_boundary_vertices,
                      layout->max_no_goods_size,
                      layout->flags);
}

static
bool read_layout_fn(int socket, void *data) {
  uint32_t num_vertices, num_quads, num_hexes,
    num_symmetries, num_boundary_vertices, max_no_goods_size;
  uint8_t flags;
  return read_layout(always_true, NULL, socket,
                     &num_vertices, &num_quads, &num_hexes,
                     &num_symmetries, &num_boundary_vertices,
                     &max_no_goods_size, &flags);
}

START_TEST(test_send_layout) {
  for (size_t i = 0; i < 256; i++) {
    subproblem_layout layout;
    layout.num_vertices = rand() % 256;
    layout.num_quads = rand() % 256;
    layout.num_hexes = rand() % 256;
    layout.num_symmetries = rand() % 256;
    layout.num_boundary_vertices = rand() % 256;
    layout.max_no_goods_size = rand() % 256;
    layout.flags = 0;
    if (rand() % 2 == 0) layout.flags |= SOLVER_SHELLABLE_ONLY;
    if (rand() % 2 == 0) layout.flags |= SOLVER_BIPARTITE_GRAPH;

    io_state state;
    run_io(&state, write_layout_fn, &layout);

    uint32_t num_vertices, num_quads, num_hexes,
      num_symmetries, num_boundary_vertices, max_no_goods_size;
    uint8_t flags;
    ck_assert(read_layout(always_true, NULL, state.socket,
                          &num_vertices, &num_quads, &num_hexes,
                          &num_symmetries, &num_boundary_vertices,
                          &max_no_goods_size, &flags));
    ck_assert_int_eq(num_vertices, layout.num_vertices);
    ck_assert_int_eq(num_quads, layout.num_quads);
    ck_assert_int_eq(num_hexes, layout.num_hexes);
    ck_assert_int_eq(num_symmetries, layout.num_symmetries);
    ck_assert_int_eq(num_boundary_vertices, layout.num_boundary_vertices);
    ck_assert_int_eq(max_no_goods_size, layout.max_no_goods_size);
    ck_assert_int_eq(flags, layout.flags);
    char buf;
    ck_assert(read(state.socket, &buf, 1) == 0);
    end_io(&state, STATUS_SUCCESS);
  }
} END_TEST

START_TEST(test_interrupt_read_layout) {
  for (size_t i = 0; i < 256; i++) {
    subproblem_layout layout;
    layout.num_vertices = rand() % 256;
    layout.num_quads = rand() % 256;
    layout.num_hexes = rand() % 256;
    layout.num_symmetries = rand() % 256;
    layout.num_boundary_vertices = rand() % 256;
    layout.max_no_goods_size = rand() % 256;
    layout.flags = 0;
    if (rand() % 2 == 0) layout.flags |= SOLVER_SHELLABLE_ONLY;
    if (rand() % 2 == 0) layout.flags |= SOLVER_BIPARTITE_GRAPH;

    io_state state;
    run_io(&state, write_layout_fn, &layout);

    uint32_t num_vertices, num_quads, num_hexes,
      num_symmetries, num_boundary_vertices, max_no_goods_size;
    uint8_t flags;
    ck_assert(!read_layout(always_false, NULL, state.socket,
                           &num_vertices, &num_quads, &num_hexes,
                           &num_symmetries, &num_boundary_vertices,
                           &max_no_goods_size, &flags));
    end_io(&state, STATUS_IGNORE);
  }
} END_TEST

START_TEST(test_interrupt_write_layout) {
  for (size_t i = 0; i < 256; i++) {
    subproblem_layout layout;
    layout.num_vertices = rand() % 256;
    layout.num_quads = rand() % 256;
    layout.num_hexes = rand() % 256;
    layout.num_symmetries = rand() % 256;
    layout.num_boundary_vertices = rand() % 256;
    layout.max_no_goods_size = rand() % 256;
    layout.flags = 0;
    if (rand() % 2 == 0) layout.flags |= SOLVER_SHELLABLE_ONLY;
    if (rand() % 2 == 0) layout.flags |= SOLVER_BIPARTITE_GRAPH;

    io_state state;
    run_io(&state, read_layout_fn, NULL);

    ck_assert(!write_layout(always_false, NULL, state.socket,
                            layout.num_vertices, layout.num_quads, layout.num_hexes,
                            layout.num_symmetries, layout.num_boundary_vertices,
                            layout.max_no_goods_size, layout.flags));
    end_io(&state, STATUS_FAILURE);
  }
} END_TEST

START_TEST(test_read_layout_partial_write) {
  for (size_t i = 0; i < 256; i++) {
    subproblem_layout layout;
    layout.num_vertices = rand() % 256;
    layout.num_quads = rand() % 256;
    layout.num_hexes = rand() % 256;
    layout.num_symmetries = rand() % 256;
    layout.num_boundary_vertices = rand() % 256;
    layout.max_no_goods_size = rand() % 256;
    layout.flags = 0;
    if (rand() % 2 == 0) layout.flags |= SOLVER_SHELLABLE_ONLY;
    if (rand() % 2 == 0) layout.flags |= SOLVER_BIPARTITE_GRAPH;

    partial_write_arg write_arg = {write_layout_fn, &layout};

    io_state state;
    run_io(&state, partial_write, &write_arg);

    uint32_t num_vertices, num_quads, num_hexes,
      num_symmetries, num_boundary_vertices, max_no_goods_size;
    uint8_t flags;
    ck_assert(!read_layout(always_true, NULL, state.socket,
                           &num_vertices, &num_quads, &num_hexes,
                           &num_symmetries, &num_boundary_vertices,
                           &max_no_goods_size, &flags));
    end_io(&state, STATUS_SUCCESS);
  }
} END_TEST

typedef struct solution {
  hex_index size;
  vertex_index *vertices;
} solution;

static
bool write_solution_fn(int socket, void *arg) {
  solution *sol = arg;

  uint8_t buffer[4096];
  return write_solution(always_true, NULL, socket,
                        buffer, sol->vertices, sol->size);
}

static
bool read_solution_fn(int socket, void *data) {
  uint8_t buffer[4096];
  hex_index size;
  vertex_index *vertices;
  return read_solution(always_true, NULL, socket,
                       buffer, &vertices, &size, 255);
}

static void try_read_solution(
  size_t max_hexes,
  hex_index num_hexes, vertex_index *vertices,
  expected_status expected_status) {
  solution sol = {num_hexes, vertices};

  io_state state;
  run_io(&state, write_solution_fn, &sol);

  uint8_t buffer[8*max_hexes+1];
  hex_index received_num_hexes;
  vertex_index *received_vertices;
  bool status = read_solution(always_true, NULL, state.socket,
                              buffer, &received_vertices,
                              &received_num_hexes, max_hexes);
  if (expected_status == STATUS_SUCCESS) {
    ck_assert(status);

    ck_assert_int_eq(num_hexes, received_num_hexes);
    ck_assert_mem_eq(vertices, received_vertices, sizeof(vertices));

    char eof_buf;
    ck_assert(read(state.socket, &eof_buf, sizeof(eof_buf)) == 0);
  }
  else
    ck_assert(!status);

  end_io(&state, expected_status);
}

START_TEST(test_send_solution) {
  for (size_t i = 0; i < 256; i++) {
    uint32_t num_hexes = rand() % 256;
    vertex_index vertices[8*num_hexes];
    for (uint32_t i = 0; i < 8 * num_hexes; i++)
      vertices[i] = rand() % 256;

    try_read_solution(255, num_hexes, vertices, STATUS_SUCCESS);
    try_read_solution(num_hexes, num_hexes, vertices, STATUS_SUCCESS);
    try_read_solution(num_hexes + 1, num_hexes, vertices, STATUS_SUCCESS);
  }
} END_TEST

START_TEST(test_send_solution_small_buffer) {
  for (size_t i = 0; i < 256; i++) {
    uint32_t num_hexes = 1 + rand() % 255;
    vertex_index vertices[8*num_hexes];
    for (uint32_t i = 0; i < 8 * num_hexes; i++)
      vertices[i] = rand() % 256;

    try_read_solution(num_hexes - 1, num_hexes, vertices, STATUS_IGNORE);
  }
} END_TEST

START_TEST(test_interrupt_read_solution) {
  for (size_t i = 0; i < 256; i++) {
    uint32_t num_hexes = rand() % 256;
    vertex_index vertices[8*num_hexes];
    for (uint32_t i = 0; i < 8 * num_hexes; i++)
      vertices[i] = rand() % 256;

    solution sol = {num_hexes, vertices};

    io_state state;
    run_io(&state, write_solution_fn, &sol);

    uint8_t buffer[4096];
    hex_index received_num_hexes;
    vertex_index *received_vertices;
    ck_assert(!read_solution(always_false, NULL, state.socket,
                             buffer, &received_vertices,
                             &received_num_hexes, 255));
    end_io(&state, STATUS_IGNORE);
  }
} END_TEST

START_TEST(test_interrupt_write_solution) {
  for (size_t i = 0; i < 256; i++) {
    uint32_t num_hexes = rand() % 256;
    vertex_index vertices[8*num_hexes];
    for (uint32_t i = 0; i < 8 * num_hexes; i++)
      vertices[i] = rand() % 256;

    io_state state;
    run_io(&state, read_solution_fn, NULL);

    uint8_t buffer[4096];
    ck_assert(!write_solution(always_false, NULL, state.socket,
                              buffer, vertices, num_hexes));
    end_io(&state, STATUS_FAILURE);
  }
} END_TEST

START_TEST(test_read_solution_partial_write) {
  for (size_t i = 0; i < 256; i++) {
    uint32_t num_hexes = rand() % 256;
    vertex_index vertices[8*num_hexes];
    for (uint32_t i = 0; i < 8 * num_hexes; i++)
      vertices[i] = rand() % 256;

    solution sol = {num_hexes, vertices};

    partial_write_arg arg = {write_solution_fn, &sol};

    io_state state;
    run_io(&state, partial_write, &arg);

    uint8_t buffer[4096];
    hex_index received_num_hexes;
    vertex_index *received_vertices;
    ck_assert(!read_solution(always_true, NULL, state.socket,
                             buffer, &received_vertices,
                             &received_num_hexes, 255));
    end_io(&state, STATUS_SUCCESS);
  }
} END_TEST

const char *meshes[] = {
  INPUT_DIR "/hex.mesh",
  INPUT_DIR "/prism.mesh",
  INPUT_DIR "/pyramid.mesh",
  INPUT_DIR "/single-hex.mesh",
  INPUT_DIR "/spindle.mesh",
  INPUT_DIR "/tetrahedron.mesh",
  INPUT_DIR "/two-hex.mesh",
};

static
void on_solution(const solver *solver, uint32_t num_hexes, void *data) {}

static
void random_solver(mesh *mesh, solver *solver) {
  size_t mesh_id = rand() % (sizeof(meshes)/sizeof(*meshes));
  ck_assert(mesh_load(mesh, meshes[mesh_id]) == SUCCESS);

  hex_index num_hexes = mesh->num_boundary_quads / 6;
  hex_index max_hexes = (255 - mesh->num_boundary_quads)/3;
  num_hexes += rand() % (max_hexes - num_hexes);
  ck_assert(mesh_reserve(mesh, num_hexes) == SUCCESS);

  vertex_index num_internal_vertices = rand() % 10;
  ck_assert(solver_init(solver, mesh, mesh->num_vertices +
                        num_internal_vertices, 0) == SUCCESS);

  size_t num_steps = rand() % 16;

  solver->cb = on_solution;
  solver->user_data = NULL;

  if (!propagate(solver)) return;

  for (size_t i = 0; i < num_steps; i++) {
    vertex_set domain;
    quad_vertex_index vertex_id = choose_variable(solver, &domain);

    size_t num_set = vertex_set_size(&solver->domains[vertex_id]);
    if (num_set == 0) break;
    size_t to_pick = rand() % num_set;
    size_t i = 0;

    vertex_index value = 0;
    VERTEX_SET_FOREACH(solver->domains[vertex_id], x) {
      if (i++ == to_pick) {
        value = x;
      }
    }

    vertex_set new_domain;
    vertex_set_init_empty(&new_domain);
    vertex_set_insert(&new_domain, value);
    solver->domains[vertex_id] = new_domain;

    if (!propagate(solver)) break;
  }
}

static
void compare_subproblems(solver *solver_a, solver *solver_b) {
  ck_assert_int_eq(solver_a->hex_id, solver_b->hex_id);

  ck_assert_int_eq(solver_a->num_vertices, solver_b->num_vertices);
  ck_assert_int_eq(solver_a->num_quads, solver_b->num_quads);
  ck_assert_int_eq(solver_a->num_hexes, solver_b->num_hexes);

  ck_assert_int_eq(solver_a->max_quad_id, solver_b->max_quad_id);
  ck_assert_int_eq(solver_a->max_vertex_id, solver_b->max_vertex_id);

  for (size_t i = 0; i < 4*solver_a->max_quad_id; i++)
    ck_assert_int_eq(solver_a->quads[i], solver_b->quads[i]);

  for (size_t i = 0; i < 8ul * solver_a->hex_id + 4; i++) {
    ck_assert_int_eq(solver_a->hexes[i], solver_b->hexes[i]);
  }

  for (size_t i = 0; i < 4; i++) {
    if (solver_a->bound_mask & (1 << i)) {
      ck_assert_int_eq(solver_a->hexes[8 * solver_a->hex_id + 4 + i],
                       solver_b->hexes[8 * solver_a->hex_id + 4 + i]);
    }
  }

  ck_assert_int_eq(solver_a->quad_queue_size - solver_a->quad_queue_offset,
                   solver_b->quad_queue_size - solver_b->quad_queue_offset);

  for (uint32_t i = solver_a->quad_queue_offset,
         j = solver_b->quad_queue_offset;
       i < solver_a->quad_queue_size; i++, j++) {
    ck_assert_int_eq(solver_a->quad_queue[i], solver_b->quad_queue[j]);
  }

  for (uint32_t i = 0; i < solver_a->num_vertices*solver_a->num_vertices; i++) {
    ck_assert_int_eq(solver_a->quad_descriptors[i],
                     solver_b->quad_descriptors[i]);
  }

  for (vertex_index i = 0; i < solver_a->num_vertices; i++) {
    for (size_t j = 0; j < VERTEX_SET_NUM_WORDS; j++) {
      ck_assert_int_eq(solver_a->allowed_neighbors[i].data[j],
                       solver_b->allowed_neighbors[i].data[j]);
    }
  }

  for (vertex_index i = 0; i < solver_a->num_vertices; i++) {
    for (size_t j = 0; j < VERTEX_SET_NUM_WORDS; j++) {
      ck_assert_int_eq(solver_a->known_neighbors[i].data[j],
                       solver_b->known_neighbors[i].data[j]);
    }
  }

  for (vertex_index i = 0; i < solver_a->num_vertices; i++) {
    for (size_t j = 0; j < VERTEX_SET_NUM_WORDS; j++) {
      ck_assert_int_eq(solver_a->known_diagonals[i].data[j],
                       solver_b->known_diagonals[i].data[j]);
    }
  }

  for (hex_vertex_index i = 0; i < 4; i++) {
    for (size_t j = 0; j < VERTEX_SET_NUM_WORDS; j++) {
      ck_assert_int_eq(solver_a->domains[i].data[j],
                       solver_b->domains[i].data[j]);
    }
  }

  ck_assert_int_eq(solver_a->bound_mask, solver_b->bound_mask);

  if (solver_a->num_symmetries != 0) {
    ck_assert_int_eq(solver_a->num_symmetries, solver_b->num_symmetries);
    ck_assert_int_eq(solver_a->num_boundary_vertices,
                     solver_b->num_boundary_vertices);
  }
}

typedef struct subproblem {
  mesh *mesh;
  solver *solver;
} subproblem;

static
bool write_subproblem_fn(int socket, void *arg) {
  subproblem *problem = arg;

  subproblem_layout layout;
  compute_subproblem_layout(&layout, problem->solver->num_vertices,
                            problem->solver->num_quads,
                            problem->solver->num_hexes,
                            problem->solver->num_symmetries,
                            problem->solver->num_boundary_vertices,
                            problem->solver->no_goods_capacity,
                            problem->solver->flags);

  uint8_t buffer[layout.packet_size];
  bool status = write_subproblem(always_true, NULL, socket,
                                 buffer, &layout,
                                 problem->solver);

  solver_release(problem->solver);
  mesh_release(problem->mesh);

  return status;
}

START_TEST(test_send_subproblem) {
  for (size_t i = 0; i < 16; i++) {
    mesh mesh;
    solver solver;
    random_solver(&mesh, &solver);

    struct solver_pool pool;
    ck_assert(solver_pool_alloc(
                &pool, solver.num_vertices, solver.num_quads, solver.num_hexes,
                solver.num_symmetries, solver.num_boundary_vertices, false,
                1)
              == SUCCESS);

    subproblem problem = {&mesh, &solver};
    io_state state;
    run_io(&state, write_subproblem_fn, &problem);

    subproblem_layout layout;
    compute_subproblem_layout(
      &layout, solver.num_vertices, solver.num_quads, solver.num_hexes,
      solver.num_symmetries, solver.num_boundary_vertices,
      solver.no_goods_capacity, solver.flags);

    uint8_t buffer[layout.packet_size];
    ck_assert(read_subproblem(always_true, NULL, state.socket,
                              buffer, &layout, solver_pool_get(&pool, 0)));
    end_io(&state, STATUS_SUCCESS);

    compare_subproblems(&solver, solver_pool_get(&pool, 0));

    solver_pool_release(&pool);

    solver_release(&solver);
    mesh_release(&mesh);
  }
} END_TEST

Suite *protocol_suite(void) {
  Suite *s = suite_create("Distributed protocol");

  TCase *tcase = tcase_create("Read/Write Name");
  tcase_add_test(tcase, test_send_name);
  tcase_add_test(tcase, test_send_name_small_buffer);
  tcase_add_test(tcase, test_interrupt_read_name);
  tcase_add_test(tcase, test_interrupt_write_name);
  tcase_add_test(tcase, test_read_name_partial_write);
  suite_add_tcase(s, tcase);

  tcase = tcase_create("Read/Write Command");
  tcase_add_test(tcase, test_send_command);
  tcase_add_test(tcase, test_interrupt_read_command);
  tcase_add_test(tcase, test_interrupt_write_command);
  tcase_add_test(tcase, test_read_command_partial_write);
  suite_add_tcase(s, tcase);

  tcase = tcase_create("Read/Write Layout");
  tcase_add_test(tcase, test_send_layout);
  tcase_add_test(tcase, test_interrupt_read_layout);
  tcase_add_test(tcase, test_interrupt_write_layout);
  tcase_add_test(tcase, test_read_layout_partial_write);
  suite_add_tcase(s, tcase);

  tcase = tcase_create("Read/Write Solution");
  tcase_add_test(tcase, test_send_solution);
  tcase_add_test(tcase, test_send_solution_small_buffer);
  tcase_add_test(tcase, test_interrupt_read_solution);
  tcase_add_test(tcase, test_interrupt_write_solution);
  tcase_add_test(tcase, test_read_solution_partial_write);
  suite_add_tcase(s, tcase);

  tcase = tcase_create("Read/Write Subproblem");
  tcase_add_test(tcase, test_send_subproblem);
  suite_add_tcase(s, tcase);

  return s;
}
